-- MonsterM
-- Create by chengb Jan/14/2015
-- 怪物模块

module("MonsterM", package.seeall);

-- 怪物初始化的回调
local initMonsterCallback = {};

-- 配置表
local monsterTable = {};
local monsterInitTable = {};

--内部函数声明
local whenOpenGrid;
local whenUseSpell;
local whenBossShow;
local whenGridRefreshMonster;
local whenEnterUnreal;
local whenEnterInstance;
local whenUseStoneMagic;
local whenMonsterTransform;
local whenStartDungeon;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    monsterTable = {};
    monsterInitTable = {};

    -- 转换monster_CSV表
    local monster_CSV = get_monster_CSV();
    for i = 1, #monster_CSV do
        local classId = monster_CSV[i].class_id;
        monsterTable[classId] = monster_CSV[i];

        -- 解析动作帧范围
        parseActionFrameRange(monster_CSV[i]);
    end
    monster_CSV = {};

    -- 转换monster_init_CSV表
    local monster_init_CSV = get_monster_init_CSV();
    for i = 1, #monster_init_CSV do
        local rule = monster_init_CSV[i].rule;
        if not monsterInitTable[rule] then
            monsterInitTable[rule] = {};
        end
        table.insert(monsterInitTable[rule], monster_init_CSV[i]);
    end
    monster_init_CSV = {};
end

-- 模块初始化
function init()
    loadCsv();

    EventMgr.removeAll("MonsterM");
    EventMgr.register("MonsterM", event.USE_SPELL, whenUseSpell);
    EventMgr.register("MonsterM", event.USE_STONE_MAGIC, whenUseStoneMagic);

    -- 关注开格子事件
    EventMgr.register("MonsterM", event.OPEN_GRID, whenOpenGrid);
    EventMgr.register("MonsterM", event.PURE_OPEN_GRID, whenOpenGrid);

    -- 关注格子刷怪事件
    EventMgr.register("MonsterM", event.GRID_REFRESH_MONSTER, whenGridRefreshMonster);

    EventMgr.register("MonsterM", event.BOSS_SHOW, whenBossShow);

    -- 关注进入夹层的事件
    EventMgr.register("MonsterM", event.ENTER_UNREAL_DUNGEON, whenEnterUnreal);

    -- 关注进入副本的事件
    EventMgr.register("MonsterM", event.ENTER_DUNGEON_INSTANCE, whenEnterInstance);

    -- 关注怪物变身事件
    EventMgr.register("MonsterM", event.MONSTER_TRANSFORM, whenMonsterTransform);

    -- 关注迷宫开始的事件(这里暂时只处理天空之城定制副本)
    EventMgr.register("MonsterM", event.START_DUNGEON, function(layer)
        whenStartDungeon();
    end);
end

-- 释放石柱技能回调
whenUseStoneMagic = function(para)
    -- 怪物魔法反射
    for pos = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(pos);
        local monster = grid.monster;

        -- 在场的未死亡怪物
        if grid:isOpened() and monster and not monster:isDead() then
            -- 反射一下
            PropM.trigger(monster, "reflex_magic_damage", 1, ME.user);
        end
    end
end

-- 放技能回调
whenUseSpell = function(para)
    local skillId = para.skillId;
    local dbase = SkillM.query(skillId, "dbase") or {};
    if dbase["no_reflex"] == 1 then
        -- 不会被反射
        return;
    end

    -- 怪物魔法反射
    for pos = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(pos);
        local monster = grid.monster;

        -- 在场的未死亡怪物
        if grid:isOpened() and monster and not monster:isDead() then
            -- 反射一下
            PropM.trigger(monster, "reflex_magic_damage", 1, ME.user);
        end
    end
end

-- 打开格子时判断格子的怪物是否出现
whenOpenGrid = function(args)
    local pos = args.pos;
    local grid = DungeonM.getGridByPos(pos);

    if grid:isMonster() then
        -- 怪物第一次出现弹出怪物信息
        local classId = grid.monster:query("class_id");
        setMonsterAppear(classId, grid);

        -- 触发伤害
        local props = PropM.fetchProps(grid.monster, "apper_damage");
        for _, prop in ipairs(props) do
            PropM.trigger(grid.monster, prop[1], prop[2], { ME.user, -1, });
        end
    end
end

-- 格子上刷怪
whenGridRefreshMonster = function(args)
    local pos = args.pos;
    local grid = DungeonM.getGridByPos(pos);

    if grid:isMonster() then
        -- 怪物第一次出现弹出怪物信息
        local classId = grid.monster:query("class_id");
        setMonsterAppear(classId, grid);
    end
end

-- 怪物变身的回调
whenMonsterTransform = function(args)
    local pos     = args.pos;
    local grid    = DungeonM.getGridByPos(pos);
    local classId = args.class_id;

    setMonsterAppear(classId, grid);
end

-- BOSS出现的时候检查下boss出现提醒
whenBossShow = function()
    local dungeonId = DungeonM.getDungeonId();
    local layer     = DungeonM.currentLayer();

    if DungeonBossM.isBossLayer(dungeonId, layer) then
        local bossReminder = ME.user.dbase:queryTemp("boss_appear_reminder") or {};
        for _, args in pairs(bossReminder) do
            MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_MONSTER, args);
        end

        ME.user.dbase:deleteTemp("boss_appear_reminder");
    end
end

-- 进入夹层迷宫
whenEnterUnreal = function(args)
    local unrealId = UnrealDungeonM.getUnrealId();
    local dbase = UnrealDungeonM.query(unrealId, "dbase");
    local wishInfo = ME.user.dbase:query("wish_info", {});
    local optionTimes = wishInfo["option_times"] or {};

    -- 没有配置弹出的，或者已经选择过的愿望
    if type(dbase) ~= "table" or dbase["show_monster_appear"] ~= 1 or optionTimes[unrealId] ~= nil then
        return;
    end

    -- 弹出怪物出现提示框，只弹一次
    local grids = DungeonM.getCurrentDungeon();
    for _, grid in pairs(grids) do
        if grid:isMonster() then
            local classId = grid.monster:query("class_id");

            local args = { ["id"] = classId, ["monster"] = table.deepcopy(grid.monster), ["isBoss"] = grid.monster:isBoss(), };
            MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_MONSTER, args);
            break;
        end
    end
end

-- 进入副本
whenEnterInstance = function(args)
    -- 弹出怪物出现提示框，只弹一次
    local grids = DungeonM.getCurrentDungeon();
    for _, grid in pairs(grids) do
        if grid:isMonster() then
            local classId = grid.monster:query("class_id");
            setMonsterAppear(classId, grid);
        end
    end
end

whenStartDungeon = function(args)
    local dungeonId = DungeonM.getDungeonId();
    local layer     = DungeonM.currentLayer();

    if SkyInstanceM.isInSkyInstance() then
        -- 在定制层需要判断
        local allOpenLayers = SkyInstanceM.query(dungeonId, "all_open_layers");
        if type(allOpenLayers) == "table" and
            table.indexOf(allOpenLayers, layer) ~= -1 then
            -- 弹出怪物出现提示框，只弹一次
            local grids = DungeonM.getCurrentDungeon();
            for _, grid in pairs(grids) do
                if grid:isMonster() then
                    local classId = grid.monster:query("class_id");
                    setMonsterAppear(classId, grid);
                end
            end
            return;
        end
    end

    -- 检查是否有新出现怪物
    if DungeonBossM.isBossLayer(dungeonId, layer) then
        -- 是boss层
        local grids = DungeonM.getCurrentDungeon();
        for _, grid in pairs(grids) do
            if grid:isMonster() then
                -- 怪物第一次出现弹出怪物信息
                local classId = grid.monster:query("class_id");
                setMonsterAppear(classId, grid, true);
            end
        end
    end
end

-- 注册初始化怪物回调
function registerInitCallback(key, func)
    initMonsterCallback[key] = func;
end

-- 检索某个怪物的配置数据
function query(classId, path)
    local m = monsterTable[classId]

    if not m or path == "" or not path then
        return m;
    end

    return m[path];
end

-- 解析动作的帧范围
function parseActionFrameRange(info)
    -- 待机、受创、死亡、攻击
    local fields = { "idle_frame", "damaged_frame", "die_frame", "attack_frame", "strike_back_frame", "appear_frame"};

    for _, field in pairs(fields) do
        local range = info[field];
        if not string.startWith(range, "[") or not string.endWith(range, "]") then
            error(string.format("字段(%s)配置格式错误!", tostring(field)));
            return info;
        end

        local arr = string.explode(string.sub(range, 2, -2),",");
        if #arr ~= 2 then
            error(string.format("字段(%s)配置格式错误!", tostring(field)));
            return info;
        end

        info[field] = { tonumber(arr[1]), tonumber(arr[2]) };
    end

    return info;
end

--------------------------
-- 地牢怪物变身
-- @param monster   要变身的怪物
-- @param classId   怪物编号
-- @param para      额外参数（这里的参数会被吸入怪物的dbase中）
function dungeonMonsterTransform(monster, classId, para, noInit)
    local dbase = {
        ["class_id"] = classId,
        ["skills"]   = query(classId, "skills"),
        ["style"]    = query(classId, "style"),
        ["max_hp"]   = query(classId, "dbase")["hp"],
    };

    -- 吸入额外参数
    dbase = table.add(dbase, para);

    monster:setClassId(classId);
    monster.dbase:aborb(dbase);

    -- 重新初始化怪物
    local rule = query(classId, "init_rule");
    if not rule then
        error("未配置怪物初始化规则，无法初始化怪物属性。");
        return monster;
    end

    if noInit ~= true then
        initDungeonMonster(monster, rule, para["layer"], DungeonAreaM.query(DungeonM.getDungeonId(), "layer_fix"));
    end

    -- 初始化怪物回调
    for _, f in pairs(initMonsterCallback) do
        if type(f) == "function" then
            f(monster);
        end
    end

    -- 刷新下属性
    PropM.refresh(monster);
    return monster;
end

--------------------------
-- 创建地牢怪物
-- @param classId   怪物编号
-- @param para      额外参数（这里的参数会被吸入怪物的dbase中）
function createDungeonMonster(classId, para)

    -- 必须传入地牢层数
    assert(type(para["layer"]) == "number");

    -- 数据要先深复制一下再给怪物使用，免得某些技能或者策略修改怪物属性的时候，
    -- MonsterM中的数据会被同步修改
    local dbase = {
        ["class_id"] = classId,
        ["skills"]   = table.deepcopy(query(classId, "skills")),
    };

    -- 吸入额外参数
    dbase = table.add(dbase, para);

    local monster = Monster.new(classId, dbase);

    -- 初始化怪物
    local rule = query(classId, "init_rule");
    if not rule then
        error("未配置怪物(%d)初始化规则，无法初始化怪物属性。", classId);
        return monster;
    end
    initDungeonMonster(monster, rule, para["layer"], DungeonAreaM.query(DungeonM.getDungeonId(), "layer_fix"));

    -- 初始化怪物回调
    for _, f in pairs(initMonsterCallback) do
        if type(f) == "function" then
            f(monster);
        end
    end

    return monster;
end

-- 初始化地牢怪物
function initDungeonMonster(monster, rule, layer, layer_fix)
    local arr = monsterInitTable[rule];
    if not arr then
        cclog("规则不存在，无法初始化怪物。");
        return;
    end

    local dbase = query(monster.classId, "dbase");
    for _, m in pairs(arr) do
        -- 取属性字段
        local attrib = m["attrib"];
        local extraArgs = m["extra_args"];
        local prefix = "formula_";
        local value;
        local coef;

        -- 如果配置了公式，则调用公式计算，否则直接取配置的值
        if type(m["calc_formula"]) == "string" and
           string.startWith(m["calc_formula"], prefix) then
            local formulaName = string.sub(m["calc_formula"], string.len(prefix) + 1);
            local coef = dbase[attrib] or 0;
            if attrib == "max_hp" then
                -- 最大血量的，需要处理下
                coef = dbase["hp"] or 0;
            end

            -- 检查额外系数
            if extraArgs and extraArgs ~= "" then
                local coef2 = dbase[extraArgs] or 0;
                value = FormulaM.invoke(formulaName, coef, coef2, layer, layer_fix);
            else
                value = FormulaM.invoke(formulaName, coef, layer, layer_fix);
            end
        else
            value = m["calc_formula"];
        end

        monster.dbase:set(attrib, value);
     end

    -- 最大血量
    -- local maxHp = monster:getHp();
    -- monster.dbase:set("max_hp", maxHp);
end

-- 获取怪物发言内容
function getTalkId()
    return TalkTemplateM.getTalkId(GRID_TYPE_MONSTER, 1);
end

-- 当前怪物出现过
function setMonsterAppear(id, grid, notNotify)
    local bufferId = query(id, "buffer_id");
    local baseClassId = query(id, "base_class_id");
    local isBoss = query(id, "monster_type") == MONSTER_TYPE_BOSS;

    local state = BuffDataM.query("monster_appear_log", bufferId) or 0;

    if state == 0 then
        BuffDataM.set("monster_appear_log", bufferId, 1);

        -- 蜘蛛， 蝙蝠，野猪不出现
        -- 抛出事件
        local  a = {1, 6, 22};
        if table.indexOf(a, baseClassId) == -1 then
            local args = { ["id"] = id, ["monster"] = table.deepcopy(grid.monster), ["isBoss"] = isBoss, };

            if notNotify then
                -- boss层的先不通知，event.BOSS_SHOW再通知
                local bossReminder = ME.user.dbase:queryTemp("boss_appear_reminder") or {};
                table.insert(bossReminder, args);

                ME.user.dbase:setTemp("boss_appear_reminder", bossReminder);
            else
                MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_MONSTER, args);
            end
        end
    end
end

-- 能否击杀触发
function wouldKillTrigger(source, monster)
    -- 玩家击杀怪物才会触发
    if monster.type ~= OBJECT_TYPE_MONSTER then
        return false;
    end

    return true;
end

-- 获取怪物系别
function getStyleName(classId)
    local style = query(classId, "style");

    if style == MONSTER_STYLE_UNDEAD then
        return getLocStr("monster_style_undead");
    elseif style == MONSTER_STYLE_LIVING then
        return getLocStr("monster_style_living");
    elseif style == MONSTER_STYLE_ELEMENT then
        return getLocStr("monster_style_element");
    elseif style == MONSTER_STYLE_PLANT then
        return getLocStr("monster_style_plant");
    elseif style == MONSTER_STYLE_HUMAN then
        return getLocStr("monster_style_human");
    elseif style == MONSTER_STYLE_DEMON then
        return getLocStr("monster_style_demon");
    elseif style == MONSTER_STYLE_MACHINE then
        return getLocStr("monster_style_machine");
    else
        return getLocStr("monster_style_slime");
    end
end

-- 通知怪物死亡
function notifyDeath(monster)
    local combine = PropM.combine;
    local apply = PropM.apply;
    local fetchProps = PropM.fetchProps;
    local trigger = PropM.trigger;

    local self = monster;
    -- 将格子设置为无效
    local pos = self:getOwner();
    local grids = DungeonM.getCurrentDungeon();
    grids[pos]:changeState(GRID_STATE_DISABLE);

    -- 刷新下光环
    AssistMonsterM.whenMonsterDie(monster);

    -- 抛出怪物死亡事件
    EventMgr.fire(event.MONSTER_DIE_LOGIC, { ["monster"] = self, ["pos"] = self:getPos(), });

    -- 尝试清怪处理
    DungeonServiceM.monsterClear();

    -- 遍历所有死亡触发的属性
    for _, propId in ipairs(PropM.getTriggerProps("die")) do
        local row = PropM.getRow(propId);
        if row.rule ~= "revive" then
            local props = fetchProps(self, propId);
            for _, prop in ipairs(props) do
                trigger(self, prop[1], prop[2]);
            end
        end
    end

    -- 掉落龙珠
    local props = fetchProps(self, "dragon");
    for _, prop in ipairs(props) do
        trigger(self, prop[1], prop[2]);
    end

    -- 杀死指定属性敌人攻击+
    local props = fetchProps(ME.user, "kill_prop_attack");
    for _, prop in ipairs(props) do
        if prop[3] > 0 then
            local assignProp = fetchProps(self, prop[2]);
            if #assignProp > 0 then
                trigger(ME.user, prop[1], prop[2]);
            end
        end
    end

    -- 赏金猎人属性
    if self.dbase:query("track") then
        props = fetchProps(ME.user, "track");
        for _, prop in ipairs(props) do
            trigger(ME.user, prop[1], prop[2], self);
        end
    end

    -- BOSS的双抗减少
    if DungeonBossM.isInBossLayer() then
        -- 死亡的是BOSS层的流浪者
        local pos = monster:getPos();
        if BossTentM.isTrampPos(pos) then
            local grid = DungeonM.getGridByPos(DungeonBossM.getBossPos());
            local bossMonster = grid.monster;
            if bossMonster and not bossMonster:isDead() then
                prop = PropM.combine(bossMonster, "die_reduce_double_resist", 1);
                trigger(bossMonster, prop[1], prop[2], DEBUF_STATUS);
            end
        end
    end

    if self:isBoss() then
        -- 抛出boss死亡事件
        EventMgr.fire(event.BOSS_DIE, { ["monster"] = self, });
        local dungeonId = DungeonM.getDungeonId();
        local layer     = DungeonM.currentLayer();
        DungeonActionM.go("check_boss_die", dungeonId, layer);

    elseif self:isSlime() then
        -- 抛出史莱姆怪死亡事件
        EventMgr.fire(event.SLIME_MONSTER_DIE, { ["monster"] = self, });
    elseif self:isAudience() then
        -- 抛出观众怪物死亡
        EventMgr.fire(event.AUDIENCE_MONSTER_DIE, {["monster"] = self, });
    end

    -- 统计阵亡
    for pos = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(pos);
        local monster = grid.monster;

        -- 如果是已翻开的未死亡怪物
        if  grid:isOpened() and monster and
            not monster:isDead() and monster ~= self then
            -- 记录下阵亡数量
            local curr = monster.killed_count or 0;
            monster.killed_count = curr + 1;
        end
    end

    local classId = monster:getClassId();
    local dbase = query(classId, "dbase") or {};
    if type(dbase["die_stat"]) == "string" then
        -- 死亡需要统计
        BuildingBonusM.addFieldStat(dbase["die_stat"], 1);
    end
end

-- 通知服务端boss已经死亡
function checkBossDie(dungeonId, layer)
    DungeonM.addAction({["cmd"] = "check_boss_die", ["pos"] = dungeonId, ["data"] = layer}, true);

    return true, true;
end

-- 通知怪物受到伤害
function notifyReceiveDamage(monster, para)
    -- 记录下第一次受创的回合
    local round = monster.dbase:queryTemp("first_damage_round");
    if not round then
        monster.dbase:setTemp("first_damage_round");
    end
end
